<template>
  <view ref="container" class="nut-tabs" :class="[direction]">
    <template v-if="sticky">
      <nut-sticky :top="top" :container="container" @scroll="onStickyScroll">
        <view
          ref="navRef"
          class="nut-tabs__titles"
          :class="{
            'nut-tabs__titles-left': align === 'left',
            [type]: type,
            scrollable: titleScroll,
            [size]: size
          }"
          :style="tabsNavStyle"
        >
          <slot v-if="$slots.titles" name="titles"></slot>
          <template v-else>
            <view
              v-for="(item, index) in titles"
              :key="item.paneKey"
              class="nut-tabs__titles-item"
              :style="titleStyle"
              :class="{
                'nut-tabs__titles-item-left': align === 'left',
                active: item.paneKey == modelValue,
                disabled: item.disabled
              }"
              @click="tabChange(item, index)"
            >
              <view v-if="type == 'line'" class="nut-tabs__titles-item__line" :style="tabsActiveStyle"></view>
              <view v-if="type == 'smile'" class="nut-tabs__titles-item__smile" :style="tabsActiveStyle">
                <JoySmile :color="color" />
              </view>
              <view class="nut-tabs__titles-item__text" :class="{ ellipsis: ellipsis }">{{ item.title }} </view>
            </view>
          </template>
        </view>
      </nut-sticky>
    </template>
    <template v-else>
      <view
        ref="navRef"
        class="nut-tabs__titles"
        :class="{ 'nut-tabs__titles-left': align === 'left', [type]: type, scrollable: titleScroll, [size]: size }"
        :style="tabsNavStyle"
      >
        <slot v-if="$slots.titles" name="titles"></slot>
        <template v-else>
          <view
            v-for="(item, index) in titles"
            :key="item.paneKey"
            :ref="(e: any) => setTabItemRef(e as HTMLElement, index)"
            class="nut-tabs__titles-item"
            :style="titleStyle"
            :class="{
              'nut-tabs__titles-item-left': align === 'left',
              active: item.paneKey == modelValue,
              disabled: item.disabled
            }"
            @click="tabChange(item, index)"
          >
            <view v-if="type == 'line'" class="nut-tabs__titles-item__line" :style="tabsActiveStyle"></view>
            <view v-if="type == 'smile'" class="nut-tabs__titles-item__smile" :style="tabsActiveStyle">
              <JoySmile :color="color" />
            </view>
            <view class="nut-tabs__titles-item__text" :class="{ ellipsis: ellipsis }">{{ item.title }} </view>
          </view>
        </template>
      </view>
    </template>
    <view
      ref="tabsContentRef"
      class="nut-tabs__content"
      :style="contentStyle"
      @touchstart="onTouchStart"
      @touchmove="onTouchMove"
      @touchend="onTouchEnd"
      @touchcancel="onTouchEnd"
    >
      <slot name="default"></slot>
    </view>
  </view>
</template>
<script lang="ts">
import {
  onMounted,
  provide,
  VNode,
  ref,
  Ref,
  computed,
  onActivated,
  watch,
  nextTick,
  CSSProperties,
  PropType
} from 'vue'
import NutSticky from '../sticky/index.vue'
import { JoySmile } from '@nutui/icons-vue'
import { createComponent } from '@/packages/utils/create'
import { pxCheck } from '@/packages/utils/pxCheck'
import { TypeOfFun } from '@/packages/utils/util'
import { useRect } from '@/packages/utils/useRect'
import raf from '@/packages/utils/raf'
import { useTabContentTouch } from './hooks'
import { type TabsDirection, type TabsSize, type TabsType, type TabsAlign, TABS_KEY } from './types'

export class Title {
  title = ''
  titleSlot?: VNode[]
  paneKey = ''
  disabled = false
  constructor() {}
}

const { create } = createComponent('tabs')

export default create({
  components: { NutSticky, JoySmile },
  props: {
    modelValue: {
      type: [String, Number],
      default: 0
    },
    color: {
      type: String,
      default: ''
    },
    direction: {
      type: String as PropType<TabsDirection>,
      default: 'horizontal'
    },
    size: {
      type: String as PropType<TabsSize>,
      default: 'normal'
    },
    type: {
      type: String as PropType<TabsType>,
      default: 'line'
    },
    titleScroll: {
      type: Boolean,
      default: false
    },
    ellipsis: {
      type: Boolean,
      default: true
    },
    swipeable: {
      type: Boolean,
      default: false
    },
    autoHeight: {
      type: Boolean,
      default: false
    },
    background: {
      type: String,
      default: ''
    },
    animatedTime: {
      type: [Number, String],
      default: 300
    },
    titleGutter: {
      type: [Number, String],
      default: 0
    },
    sticky: {
      type: Boolean,
      default: false
    },
    top: {
      type: Number,
      default: 0
    },
    align: {
      type: String as PropType<TabsAlign>,
      default: 'center'
    }
  },
  emits: ['update:modelValue', 'click', 'change'],

  setup(props, { emit, slots }: any) {
    const container = ref<any>(null)
    let stickyFixed: boolean
    provide(TABS_KEY, {
      activeKey: computed(() => props.modelValue || '0'),
      autoHeight: computed(() => props.autoHeight),
      animatedTime: computed(() => props.animatedTime)
    })

    const titles: Ref<Title[]> = ref([])
    const renderTitles = (vnodes: VNode[]) => {
      vnodes.forEach((vnode: VNode, index: number) => {
        let type = vnode.type
        type = (type as any).name || type
        if (type == 'NutTabPane') {
          let title = new Title()
          if (vnode.props?.title || vnode.props?.['pane-key'] || vnode.props?.['paneKey']) {
            let paneKeyType = TypeOfFun(vnode.props?.['pane-key'])
            let paneIndex
              = paneKeyType == 'number' || paneKeyType == 'string' ? String(vnode.props?.['pane-key']) : null
            let camelPaneKeyType = TypeOfFun(vnode.props?.['paneKey'])
            let camelPaneIndex
              = camelPaneKeyType == 'number' || camelPaneKeyType == 'string' ? String(vnode.props?.['paneKey']) : null
            title.title = vnode.props?.title
            title.paneKey = paneIndex || camelPaneIndex || String(index)
            title.disabled = vnode.props?.disabled
          } else {
            // title.titleSlot = vnode.children?.title() as VNode[];
          }
          titles.value.push(title)
        } else {
          if (vnode.children == ' ') {
            return
          }
          renderTitles(vnode.children as VNode[])
        }
      })
    }

    const currentIndex = ref((props.modelValue as number) || 0)
    const findTabsIndex = (value: string | number) => {
      let index = titles.value.findIndex(item => item.paneKey == value)
      if (titles.value.length == 0) {
        // console.warn('[NutUI] <Tabs> 当前未找到 TabPane 组件元素 , 请检查 .');
      } else if (index == -1) {
        // console.warn('[NutUI] <Tabs> 请检查 v-model 值是否为 paneKey ,如 paneKey 未设置，请采用下标控制 .');
      } else {
        currentIndex.value = index
      }
    }

    const getScrollY = computed(() => {
      return props.titleScroll && props.direction === 'vertical'
    })
    const navRef = ref<HTMLElement>()
    const titleRef = ref([]) as Ref<HTMLElement[]>
    const scrollIntoView = (immediate?: boolean) => {
      const nav = navRef.value
      const _titles = titleRef.value
      if (!nav || !_titles || !_titles[currentIndex.value]) {
        return
      }
      const title = _titles[currentIndex.value]

      let to = 0
      if (props.direction === 'vertical') {
        const runTop = title.offsetTop - nav.offsetTop + 10
        to = runTop - (nav.offsetHeight - title.offsetHeight) / 2
      } else {
        to = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2
      }

      scrollDirection(nav, to, immediate ? 0 : 0.3, props.direction)
    }

    const scrollDirection = (nav: any, to: number, duration: number, direction: 'horizontal' | 'vertical') => {
      let count = 0
      const from = direction === 'horizontal' ? nav.scrollLeft : nav.scrollTop
      const frames = duration === 0 ? 1 : Math.round((duration * 1000) / 16)

      function animate() {
        if (direction === 'horizontal') {
          nav.scrollLeft += (to - from) / frames
        } else {
          nav.scrollTop += (to - from) / frames
        }

        if (++count < frames) {
          raf(animate)
        }
      }

      animate()
    }
    const init = (vnodes: VNode[] = slots.default?.()) => {
      titles.value = []
      vnodes = vnodes?.filter(item => typeof item.children !== 'string')
      if (vnodes && vnodes.length) {
        renderTitles(vnodes)
      }
      findTabsIndex(props.modelValue)
      nextTick(() => {
        scrollIntoView()
      })
    }
    const onStickyScroll = (params: { top: number, fixed: boolean }) => {
      stickyFixed = params.fixed
    }

    watch(
      () => slots.default?.(),
      (vnodes: VNode[]) => {
        init(vnodes)
      }
    )
    const getScrollTopRoot = () => {
      return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
    }
    watch(
      () => props.modelValue,
      (value: string | number) => {
        findTabsIndex(value)
        scrollIntoView()
        if (stickyFixed) {
          let top = useRect(container.value!).top + getScrollTopRoot()
          let value = Math.ceil(top - props.top)
          window.scrollTo({
            top: value,
            behavior: 'smooth'
          })
        }
      }
    )
    onMounted(init)
    onActivated(init)
    const tabMethods = {
      isBegin: () => {
        return currentIndex.value == 0
      },
      isEnd: () => {
        return currentIndex.value == titles.value.length - 1
      },
      next: () => {
        currentIndex.value += 1
        const nextDisabled = titles.value[currentIndex.value].disabled
        if (tabMethods.isEnd() && nextDisabled) {
          tabMethods.prev()
          return
        }
        if (nextDisabled && currentIndex.value < titles.value.length - 1) {
          tabMethods.next()
          return
        }
        tabMethods.updateValue(titles.value[currentIndex.value])
      },
      prev: () => {
        currentIndex.value -= 1
        const prevDisabled = titles.value[currentIndex.value].disabled
        if (tabMethods.isBegin() && prevDisabled) {
          tabMethods.next()
          return
        }
        if (prevDisabled && currentIndex.value > 0) {
          tabMethods.prev()
          return
        }
        tabMethods.updateValue(titles.value[currentIndex.value])
      },
      updateValue: (item: Title) => {
        emit('update:modelValue', item.paneKey)
        emit('change', item)
      },
      tabChange: (item: Title, index: number) => {
        emit('click', item)
        if (item.disabled || currentIndex.value == index) {
          return
        }
        currentIndex.value = index
        tabMethods.updateValue(item)
      },
      setTabItemRef: (el: HTMLElement, index: number) => {
        titleRef.value[index] = el
      }
    }
    const { tabsContentRef, touchState, touchMethods } = useTabContentTouch(props, tabMethods)
    const contentStyle = computed(() => {
      let offsetPercent = currentIndex.value * 100
      if (touchState.moving) {
        offsetPercent += touchState.offset
      }
      let style: CSSProperties = {
        transform:
          props.direction == 'horizontal'
            ? `translate3d(-${offsetPercent}%, 0, 0)`
            : `translate3d( 0,-${offsetPercent}%, 0)`,
        transitionDuration: touchState.moving ? undefined : `${props.animatedTime}ms`
      }
      if (props.animatedTime == 0) {
        style = {}
      }
      return style
    })
    const tabsNavStyle = computed(() => {
      return {
        background: props.background
      }
    })
    const tabsActiveStyle = computed(() => {
      return {
        color: props.type == 'smile' ? props.color : '',
        background: props.type == 'line' ? props.color : ''
      }
    })
    const titleStyle = computed(() => {
      if (!props.titleGutter) return {}
      const px = pxCheck(props.titleGutter)
      if (props.direction === 'vertical') {
        return { paddingTop: px, paddingBottom: px }
      }
      return { paddingLeft: px, paddingRight: px }
    })

    return {
      navRef,
      tabsContentRef,
      titles,
      contentStyle,
      tabsNavStyle,
      titleStyle,
      tabsActiveStyle,
      container,
      getScrollY,
      onStickyScroll,
      ...tabMethods,
      ...touchMethods
    }
  }
})
</script>
